﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace Remoting.Reactive.ProtoBuf
{
    public sealed partial class ExpressionDtoTranslator : IExpressionDtoTranslator
    {
        public Expression Translate(ExpressionDto expressionDto)
        {
            Guard.Requires<ArgumentNullException>(expressionDto != null);
            if (expressionDto == null)
                return null;
            switch (expressionDto.NodeType)
            {
                case ExpressionTypeDto.Negate:
                case ExpressionTypeDto.NegateChecked:
                case ExpressionTypeDto.Not:
                case ExpressionTypeDto.Convert:
                case ExpressionTypeDto.ConvertChecked:
                case ExpressionTypeDto.ArrayLength:
                case ExpressionTypeDto.Quote:
                case ExpressionTypeDto.TypeAs:
                    return TranslateUnary((UnaryExpressionDto)expressionDto);
                case ExpressionTypeDto.Add:
                case ExpressionTypeDto.AddChecked:
                case ExpressionTypeDto.Subtract:
                case ExpressionTypeDto.SubtractChecked:
                case ExpressionTypeDto.Multiply:
                case ExpressionTypeDto.MultiplyChecked:
                case ExpressionTypeDto.Divide:
                case ExpressionTypeDto.Modulo:
                case ExpressionTypeDto.And:
                case ExpressionTypeDto.AndAlso:
                case ExpressionTypeDto.Or:
                case ExpressionTypeDto.OrElse:
                case ExpressionTypeDto.LessThan:
                case ExpressionTypeDto.LessThanOrEqual:
                case ExpressionTypeDto.GreaterThan:
                case ExpressionTypeDto.GreaterThanOrEqual:
                case ExpressionTypeDto.Equal:
                case ExpressionTypeDto.NotEqual:
                case ExpressionTypeDto.Coalesce:
                case ExpressionTypeDto.ArrayIndex:
                case ExpressionTypeDto.RightShift:
                case ExpressionTypeDto.LeftShift:
                case ExpressionTypeDto.ExclusiveOr:
                    return TranslateBinary((BinaryExpressionDto)expressionDto);
                case ExpressionTypeDto.TypeIs:
                    return TranslateTypeIs((TypeBinaryExpressionDto)expressionDto);
                case ExpressionTypeDto.Conditional:
                    return TranslateConditional((ConditionalExpressionDto)expressionDto);
                case ExpressionTypeDto.Constant:
                    return TranslateConstant((ConstantExpressionDto)expressionDto);
                case ExpressionTypeDto.Parameter:
                    return TranslateParameter((ParameterExpressionDto)expressionDto);
                case ExpressionTypeDto.MemberAccess:
                    return TranslateMemberAccess((MemberExpressionDto)expressionDto);
                case ExpressionTypeDto.Call:
                    return TranslateMethodCall((MethodCallExpressionDto)expressionDto);
                case ExpressionTypeDto.Lambda:
                    return TranslateLambda((LambdaExpressionDto)expressionDto);
                case ExpressionTypeDto.New:
                    return TranslateNew((NewExpressionDto)expressionDto);
                case ExpressionTypeDto.NewArrayInit:
                case ExpressionTypeDto.NewArrayBounds:
                    return TranslateNewArray((NewArrayExpressionDto)expressionDto);
                case ExpressionTypeDto.Invoke:
                    return TranslateInvocation((InvocationExpressionDto)expressionDto);
                case ExpressionTypeDto.MemberInit:
                    return TranslateMemberInit((MemberInitExpressionDto)expressionDto);
                case ExpressionTypeDto.ListInit:
                    return TranslateListInit((ListInitExpressionDto)expressionDto);
                default:
                    throw new Exception(string.Format("Unhandled expression type: '{0}'", expressionDto.NodeType));
            }
        }


        private MemberBinding TranslateBinding(MemberBindingDto binding)
        {
            switch (binding.BindingType)
            {
                case MemberBindingType.Assignment:
                    return TranslateMemberAssignment((MemberAssignment)binding);
                case MemberBindingType.MemberBinding:
                    return TranslateMemberMemberBinding((MemberMemberBinding)binding);
                case MemberBindingType.ListBinding:
                    return TranslateMemberListBinding((MemberListBinding)binding);
                default:
                    throw new Exception(string.Format("Unhandled binding type '{0}'", binding.BindingType));
            }
        }

        private ElementInit TranslateElementInitializer(ElementInitDto initializer)
        {
            var arguments = TranslateExpressionList(initializer.Arguments);
            if (arguments != initializer.Arguments)
            {
                return Expression.ElementInit(initializer.AddMethod, arguments);
            }
            return initializer;
        }

        private Expression TranslateUnary(UnaryExpressionDto u)
        {
            Expression operand = Translate(u.Operand);
            if (operand != u.Operand)
            {
                return Expression.MakeUnary(u.NodeType, operand, u.Type, u.Method);
            }
            return u;
        }

        private Expression TranslateBinary(BinaryExpressionDto b)
        {
            var left = Translate(b.Left);
            var right = Translate(b.Right);
            var conversion = Translate(b.Conversion);
            if (left != b.Left || right != b.Right || conversion != b.Conversion)
            {
                if (b.NodeType == ExpressionType.Coalesce && b.Conversion != null)
                    return Expression.Coalesce(left, right, conversion as LambdaExpression);
                else
                    return Expression.MakeBinary(b.NodeType, left, right, b.IsLiftedToNull, b.Method);
            }
            return b;
        }

        private Expression TranslateTypeIs(TypeBinaryExpressionDto b)
        {
            var expr = Translate(b.Expression);
            if (expr != b.Expression)
            {
                return Expression.TypeIs(expr, b.TypeOperand);
            }
            return b;
        }

        private Expression TranslateConstant(ConstantExpression c)
        {
            return c;
        }

        private Expression TranslateConditional(ConditionalExpressionDto c)
        {
            var test = Translate(c.Test);
            var ifTrue = Translate(c.IfTrue);
            var ifFalse = Translate(c.IfFalse);
            if (test != c.Test || ifTrue != c.IfTrue || ifFalse != c.IfFalse)
            {
                return Expression.Condition(test, ifTrue, ifFalse);
            }
            return c;
        }

        private Expression TranslateParameter(ParameterExpressionDto p)
        {
            return p;
        }

        private Expression TranslateMemberAccess(MemberExpressionDto m)
        {
            var exp = Translate(m.Expression);
            if (exp != m.Expression)
            {
                return Expression.MakeMemberAccess(exp, m.Member);
            }
            return m;
        }

        private Expression TranslateMethodCall(MethodCallExpressionDto m)
        {
            var obj = Translate(m.Object);
            var args = TranslateExpressionList(m.Arguments);
            if (obj != m.Object || args != m.Arguments)
            {
                return Expression.Call(obj, m.Method, args);
            }
            return m;
        }

        private IEnumerable<ExpressionDto> TranslateExpressionList(IEnumerable<ExpressionDto> original)
        {
            List<Expression> list = null;
            for (int i = 0, n = original.Count; i < n; i++)
            {
                var p = Translate(original[i]);
                if (list != null)
                {
                    list.Add(p);
                }
                else if (p != original[i])
                {
                    list = new List<Expression>(n);
                    for (int j = 0; j < i; j++)
                    {
                        list.Add(original[j]);
                    }
                    list.Add(p);
                }
            }
            if (list != null)
            {
                return list.AsReadOnly();
            }
            return original;
        }

        public MemberAssignment TranslateMemberAssignment(MemberAssignmentDto assignment)
        {
            var e = Translate(assignment.Expression);
            if (e != assignment.Expression)
            {
                return Expression.Bind(assignment.Member, e);
            }
            return assignment;
        }

        private MemberMemberBinding TranslateMemberMemberBinding(MemberMemberBindingDto binding)
        {
            var bindings = TranslateBindingList(binding.Bindings);
            if (bindings != binding.Bindings)
            {
                return Expression.MemberBind(binding.Member, bindings);
            }
            return binding;
        }

        private MemberListBinding TranslateMemberListBinding(MemberListBindingDto binding)
        {
            var initializers = TranslateElementInitializerList(binding.Initializers);
            if (initializers != binding.Initializers)
            {
                return Expression.ListBind(binding.Member, initializers);
            }
            return binding;
        }

        private IEnumerable<MemberBindingDto> TranslateBindingList(IEnumerable<MemberBindingDto> original)
        {
            List<MemberBinding> list = null;
            for (int i = 0, n = original.Count; i < n; i++)
            {
                MemberBinding b = TranslateBinding(original[i]);
                if (list != null)
                {
                    list.Add(b);
                }
                else if (b != original[i])
                {
                    list = new List<MemberBinding>(n);
                    for (int j = 0; j < i; j++)
                    {
                        list.Add(original[j]);
                    }
                    list.Add(b);
                }
            }
            if (list != null)
                return list;
            return original;
        }

        private IEnumerable<ElementInitDto> TranslateElementInitializerList(IEnumerable<ElementInitDto> original)
        {
            List<ElementInit> list = null;
            for (int i = 0, n = original.Count; i < n; i++)
            {
                ElementInit init = TranslateElementInitializer(original[i]);
                if (list != null)
                {
                    list.Add(init);
                }
                else if (init != original[i])
                {
                    list = new List<ElementInit>(n);
                    for (int j = 0; j < i; j++)
                    {
                        list.Add(original[j]);
                    }
                    list.Add(init);
                }
            }
            if (list != null)
                return list;
            return original;
        }

        public Expression TranslateLambda(LambdaExpressionDto lambda)
        {
            var body = Translate(lambda.Body);
            if (body != lambda.Body)
            {
                return Expression.Lambda(lambda.Type, body, lambda.Parameters);
            }
            return lambda;
        }

        private NewExpression TranslateNew(NewExpressionDto nex)
        {
            var args = TranslateExpressionList(nex.Arguments);
            if (args != nex.Arguments)
            {
                if (nex.Members != null)
                    return Expression.New(nex.Constructor, args, nex.Members);
                else
                    return Expression.New(nex.Constructor, args);
            }
            return nex;
        }

        private Expression TranslateMemberInit(MemberInitExpressionDto init)
        {
            var n = TranslateNew(init.NewExpression);
            var bindings = TranslateBindingList(init.Bindings);
            if (n != init.NewExpression || bindings != init.Bindings)
            {
                return Expression.MemberInit(n, bindings);
            }
            return init;
        }

        private Expression TranslateListInit(ListInitExpressionDto init)
        {
            var n = TranslateNew(init.NewExpression);
            var initializers = TranslateElementInitializerList(init.Initializers);
            if (n != init.NewExpression || initializers != init.Initializers)
            {
                return Expression.ListInit(n, initializers);
            }
            return init;
        }

        private Expression TranslateNewArray(NewArrayExpressionDto na)
        {
            var exprs = TranslateExpressionList(na.Expressions);
            if (exprs != na.Expressions)
            {
                if (na.NodeType == ExpressionType.NewArrayInit)
                {
                    return Expression.NewArrayInit(na.Type.GetElementType(), exprs);
                }
                else
                {
                    return Expression.NewArrayBounds(na.Type.GetElementType(), exprs);
                }
            }
            return na;
        }

        private Expression TranslateInvocation(InvocationExpressionDto iv)
        {
            var args = TranslateExpressionList(iv.Arguments);
            var expr = Translate(iv.Expression);
            if (args != iv.Arguments || expr != iv.Expression)
            {
                return Expression.Invoke(expr, args);
            }
            return iv;
        }
    }
}
